package com.getan.boot.common.utils;

import lombok.AllArgsConstructor;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.MalformedInputException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;

/**
 * 路径工具类
 *
 * @author Bruce.Gong
 * @since 1.0.0-SNAPSHOT
 */
@SuppressWarnings("unused")
public class PathUtils {

    /**
     * 创建绝对路径
     *
     * <pre>
     *     of("C:/temp")
     * </pre>
     *
     * @param filePath 文件路径
     * @return Path
     */
    public static Path of(String filePath) {
        return Paths.get(filePath).normalize();
    }

    /**
     * 创建相对路径
     *
     * <pre>
     *     of("C:/temp", "hello.txt")     = C:/temp/hello.txt
     *     of("C:/temp", "./hello.txt")   = C:/temp/hello.txt
     *     of("C:/temp", "../hello.txt")  = C:/hello.txt
     * </pre>
     *
     * @param rootPath     文件路径
     * @param relativePath 文件路径
     * @return Path
     */
    public static Path of(String rootPath, String... relativePath) {
        return Paths.get(rootPath, relativePath).normalize();
    }

    /**
     * 当前路径
     *
     * @return Path
     */
    public static Path currentPath() {
        return Paths.get(StringPool.DOT);
    }

    /**
     * 当前路径的父目录
     *
     * @return Path
     */
    public static Path parentPath() {
        return Paths.get(StringPool.DOTDOT);
    }

    /**
     * 判断路径是否存在
     *
     * @param path 路径
     * @return true | false
     */
    public static boolean exists(Path path) {
        return Files.exists(path);
    }

    /**
     * 判断路径是否是目录
     *
     * @param path 路径
     * @return true | false
     */
    public static boolean isDirectory(Path path) {
        return Files.isDirectory(path);
    }

    /**
     * 判断路径是否可执行
     *
     * @param path 路径
     * @return true | false
     */
    public static boolean isExecutable(Path path) {
        return Files.isExecutable(path);
    }

    /**
     * 判断路径是否可读
     *
     * @param path 路径
     * @return true | false
     */
    public static boolean isReadable(Path path) {
        return Files.isReadable(path);
    }

    /**
     * 判断路径是否可写
     *
     * @param path 路径
     * @return true | false
     */
    public static boolean isWritable(Path path) {
        return Files.isWritable(path);
    }

    /**
     * 判断路径是否隐藏文件
     *
     * @param path 路径
     * @return true | false
     */
    public static boolean isHidden(Path path) throws IOException {
        return Files.isHidden(path);
    }

    /**
     * 删除文件
     *
     * @param path 路径
     * @throws NoSuchFileException        如果文件不存在
     * @throws DirectoryNotEmptyException 如果目录不为空
     */
    public static void delete(String path) throws IOException {
        delete(of(path));
    }

    /**
     * 删除文件
     *
     * @param path 路径
     * @throws NoSuchFileException        如果文件不存在
     * @throws DirectoryNotEmptyException 如果目录不为空
     */
    public static void delete(Path path) throws IOException {
        Files.delete(path);
    }

    /**
     * 删除文件，如果文件不存在，则返回 false
     *
     * @param path 路径
     * @throws DirectoryNotEmptyException 如果目录不为空
     */
    public static boolean deleteIfExists(String path) throws IOException {
        return deleteIfExists(of(path));
    }

    /**
     * 删除文件，如果文件不存在，则返回 false
     *
     * @param path 路径
     * @throws DirectoryNotEmptyException 如果目录不为空
     */
    public static boolean deleteIfExists(Path path) throws IOException {
        return Files.deleteIfExists(path);
    }

    /**
     * 删除目录，如果目录下还有文件或者子目录，则一并删除
     *
     * @param path 路径
     */
    public static void deleteTree(String path) throws IOException {
        deleteTree(of(path));
    }

    /**
     * 删除目录，如果目录下还有文件或者子目录，则一并删除
     *
     * @param path 路径
     */
    public static void deleteTree(Path path) throws IOException {
        if (!exists(path)) {
            return;
        }
        if (isDirectory(path)) {
            for (Path next : Files.newDirectoryStream(path)) {
                if (isDirectory(next)) {
                    deleteTree(next);
                }
                delete(next);
            }
        }
        delete(path);
    }

    /**
     * 删除目录，如果目录下还有文件或者子目录，则一并删除
     *
     * @param path 路径
     */
    private static void deleteFiles(String path, PathMatcher matcher) throws IOException {
        deleteFiles(of(path), matcher);
    }

    /**
     * 删除目录及子目录下的指定文件
     *
     * @param path 路径
     */
    private static void deleteFiles(Path path, PathMatcher matcher) throws IOException {
        Stream<Path> stream = Files.walk(path);
        Iterator<Path> iterator = stream.iterator();
        while (iterator.hasNext()) {
            Path next = iterator.next();
            if (!isDirectory(next) && matcher.matches(next)) {
                delete(next);
            }
        }
    }

    /**
     * 遍历目录及其子目录，得到所有的文件
     *
     * @param path 路径
     * @return 目录下所有的文件，包括隐藏文件，不包括文件夹
     */
    public static List<Path> listFiles(String path) throws IOException {
        return listFiles(of(path));
    }

    /**
     * 遍历目录及其子目录，得到所有的文件，如果目录不存在，则返回空，根据 matcher 对文件进行过滤
     *
     * @param path 路径
     * @return 目录下所有的文件
     */
    public static List<Path> listFiles(String path, PathMatcher matcher) throws IOException {
        return listFiles(of(path), matcher);
    }

    /**
     * 遍历目录及其子目录，得到所有的文件，如果目录不存在，则返回空
     *
     * @param path 路径
     * @return 目录下所有的文件，包括隐藏文件，不包括文件夹
     */
    public static List<Path> listFiles(Path path) throws IOException {
        return listFiles(path, p -> true);
    }

    /**
     * 遍历目录及其子目录，得到所有的文件，如果目录不存在，则返回空，根据 matcher 对文件进行过滤
     *
     * @param path 路径
     * @return 目录下所有的文件
     */
    public static List<Path> listFiles(Path path, PathMatcher matcher) throws IOException {
        if (!exists(path)) {
            return Collections.emptyList();
        }
        List<Path> files = new LinkedList<>();
        Files.walkFileTree(path, new CollectFileVisitor(files, matcher));
        for (Path file : files) {
            System.out.println(file);
        }
        return files;
    }

    /**
     * 创建目录，如果父目录不存在，则同时创建
     *
     * @param path 路径
     * @throws FileAlreadyExistsException 如果文件已存在
     */
    public static Path createDirectory(String path, FileAttribute<?>... attributes) throws IOException {
        return createDirectory(of(path), attributes);
    }

    /**
     * 创建目录，如果父目录不存在，则同时创建
     *
     * @param path 路径
     * @throws FileAlreadyExistsException 如果文件已存在
     */
    public static Path createDirectory(Path path, FileAttribute<?>... attributes) throws IOException {
        return Files.createDirectories(path, attributes);
    }

    /**
     * 创建文件，如果父目录不存在，则同时创建
     *
     * @param path 路径
     * @throws FileAlreadyExistsException 如果文件已存在
     */
    public static Path createFile(String path, FileAttribute<?>... attributes) throws IOException {
        return createFile(of(path), attributes);
    }

    /**
     * 创建文件，如果父目录不存在，则同时创建
     *
     * @param path 路径
     * @throws FileAlreadyExistsException 如果文件已存在
     */
    public static Path createFile(Path path, FileAttribute<?>... attributes) throws IOException {
        Path parent = path.getParent();
        if (!exists(parent)) {
            createDirectory(parent, attributes);
        }
        return Files.createFile(path, attributes);
    }

    /**
     * 获取文件名
     *
     * @param path 路径
     * @return 文件名
     */
    public static String getFileName(Path path) {
        return path.toFile().getName();
    }

    /**
     * 写文件，如果文件不存在，则创建，默认使用 utf8 编码
     *
     * @param path 文件
     * @throws FileAlreadyExistsException 如果文件存在
     */
    public static void writeFile(Path path, String content) throws IOException {
        if (exists(path)) {
            throw new FileAlreadyExistsException("file already exists");
        }
        try (BufferedWriter writer = Files.newBufferedWriter(path, Charsets.UTF_8)) {
            writer.write(content);
            writer.flush();
        }
    }

    /**
     * 读文件，每次读取一行，使用 lineConsumer 进行处理
     *
     * @param path 文件
     * @throws MalformedInputException 如果文件的编码不是 utf-8，则抛出异常
     * @throws NoSuchFileException     如果文件不存在，则抛出异常
     */
    public static void readFile(Path path, Consumer<String> lineConsumer) throws IOException {
        readFile(path, Charsets.UTF_8, lineConsumer);
    }

    /**
     * 读文件，每次读取一行，使用 lineConsumer 进行处理
     *
     * @param path 文件
     * @throws MalformedInputException 如果 charset 与文件的编码不匹配，则抛出异常
     * @throws NoSuchFileException     如果文件不存在，则抛出异常
     */
    public static void readFile(Path path, Charset charset, Consumer<String> lineConsumer) throws IOException {
        try (BufferedReader reader = Files.newBufferedReader(path, charset)) {
            String line;
            while ((line = reader.readLine()) != null) {
                lineConsumer.accept(line);
            }
        }
    }

    @AllArgsConstructor
    static class CollectFileVisitor extends SimpleFileVisitor<Path> {
        private final List<Path> files;
        private final PathMatcher pathMatcher;

        @Override
        public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes) throws IOException {
            if (pathMatcher.matches(path)) {
                files.add(path);
            }
            return super.visitFile(path, basicFileAttributes);
        }
    }

}
